package com.gaoding.qa.auto.help;

import com.alibaba.fastjson.JSONObject;
import com.dtflys.forest.Forest;
import com.dtflys.forest.reflection.ForestMethod;
import com.dtflys.forest.reflection.ForestVariableValue;
import com.gaoding.commons.dto.BaseDto;
import com.gaoding.commons.exception.ForbiddenException;
import com.gaoding.commons.exception.NotFoundException;
import com.gaoding.commons.util.SpringUtil;
import com.gaoding.qa.auto.api.CommonApi;
import com.gaoding.qa.auto.base.BaseGenerateTokenRequestDTO;
import com.gaoding.qa.auto.base.BaseTokenDTO;
import com.gaoding.qa.auto.base.MultiOrgTokenDTO;
import com.gaoding.qa.auto.base.MultiOrgTokenRequestDTO;
import com.gaoding.qa.auto.config.RuntimeContext;
import com.gaoding.qa.auto.enums.EnvironmentTypeEnum;
import com.gaoding.qa.auto.listener.MyListener;
import com.gaoding.qa.auto.properties.GlobalProperties;
import com.google.common.collect.Maps;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.web.client.RestTemplate;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Stack;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Supplier;

/**
 * 测试辅助类
 *
 * @author mobai
 * @since 2021/10/13 11:48
 */
@Slf4j
public final class TestHelper {

    /**
     * 请求上下文信息
     */
    private static final ThreadLocal<RuntimeContext> CONTEXT = ThreadLocal.withInitial(() -> {
        RuntimeContext runtimeContext = new RuntimeContext();
        runtimeContext.setData(MyListener.getContext());
        runtimeContext.setRuntimeHeaders(Maps.newHashMap());
        runtimeContext.setIntercept(true);
        runtimeContext.setGlobalProperties(SpringUtil.getBean(GlobalProperties.class));
        runtimeContext.setRuntimeHeadersLastLong(false);
        runtimeContext.setExpectStatusCode(MyListener.getContext().getHttpStatus());
        runtimeContext.setError(new Stack<>());
        return runtimeContext;
    });


    /**
     * 运行过程生成token
     *
     * @param account  账号
     * @param password 密码
     * @param lastLong true 则表示接下来的所有请求都会使用到本次生成的token false 只生效一次
     */
    public static void generateTokenWithMultiOrg(String account, String password, boolean lastLong, EnvironmentTypeEnum environment) {
        generateToken(account, password, 1, lastLong, environment);
    }


    /**
     * 无租户情况生成token
     *
     * @param account             账号
     * @param password            密码
     * @param lastLong            是否长期有效
     * @param environmentTypeEnum 环境
     */
    public static void generateToken(String account, String password, Boolean lastLong, EnvironmentTypeEnum environmentTypeEnum) {
        generateToken(account, password, lastLong, environmentTypeEnum, (orgList, token) -> new HashMap<String, String>() {{
            put("Authorization", "Bearer " + token.getAccessToken());
        }});
    }


    /**
     * 生成动态用户token(指定租户id,若用户没有租户会报错)
     *
     * @param account     账户
     * @param password    密码
     * @param orgId       租户id
     * @param lastLong    是否长期有效
     * @param environment 环境参数
     */
    public static void generateToken(String account, String password, String orgId, boolean lastLong, EnvironmentTypeEnum environment) {
        CommonApi commonApi = SpringUtil.getBean(CommonApi.class);
        generateToken(account, password, lastLong, environment, (orgList, token) -> {
            if (CollectionUtils.isEmpty(orgList)) {
                throw new ForbiddenException("用户没有任何租户，生成动态token失败!");
            }
            Map<String, String> map = new HashMap<>();
            BaseTokenDTO.OrgInfoDTO orgInfoDTO = orgList.stream().filter(data -> orgId.equals(data.getOrgId()))
                    .findFirst().orElseThrow(() -> new NotFoundException("虽然你指定了租户id，但是该用户并不在该租户下"));
            MultiOrgTokenRequestDTO multiOrgTokenRequestDTO = new MultiOrgTokenRequestDTO();
            multiOrgTokenRequestDTO.setAccessToken(token.getAccessToken());
            multiOrgTokenRequestDTO.setOrgId(Long.parseLong(orgInfoDTO.getOrgId()));
            multiOrgTokenRequestDTO.setClientId("qiye");
            multiOrgTokenRequestDTO.setDeviceId(token.getDeviceId());
            multiOrgTokenRequestDTO.setGrantType("org");
            MultiOrgTokenDTO multi = commonApi.multi(multiOrgTokenRequestDTO);
            map.put("Authorization", "Bearer " + multi.getAccessToken());
            return map;
        });


    }

    /**
     * 动态生成token
     *
     * @param account     账号
     * @param password    密码
     * @param orgOrder    租户序号
     * @param lastLong    是否长期有效
     * @param environment 环境
     */
    public static void generateToken(String account, String password, int orgOrder, boolean lastLong, EnvironmentTypeEnum environment) {
        CommonApi commonApi = SpringUtil.getBean(CommonApi.class);
        generateToken(account, password, lastLong, environment, (orgList, token) -> {
            Map<String, String> map = new HashMap<>(1);
            if (CollectionUtils.isNotEmpty(orgList)) {
                BaseTokenDTO.OrgInfoDTO orgInfoDTO = orgList.get(Integer.max(orgOrder - 1, 0));
                MultiOrgTokenRequestDTO multiOrgTokenRequestDTO = new MultiOrgTokenRequestDTO();
                multiOrgTokenRequestDTO.setAccessToken(token.getAccessToken());
                multiOrgTokenRequestDTO.setOrgId(Long.parseLong(orgInfoDTO.getOrgId()));
                multiOrgTokenRequestDTO.setClientId("qiye");
                multiOrgTokenRequestDTO.setDeviceId(token.getDeviceId());
                multiOrgTokenRequestDTO.setGrantType("org");
                MultiOrgTokenDTO multi = commonApi.multi(multiOrgTokenRequestDTO);
                map.put("Authorization", "Bearer " + multi.getAccessToken());
            } else {
                map.put("Authorization", "Bearer " + token.getAccessToken());
            }
            return map;

        });


    }

    public static void generateToken(String account, String password, boolean lastLong, EnvironmentTypeEnum environmentTypeEnum,
                                     BiFunction<? super List<BaseTokenDTO.OrgInfoDTO>, ? super BaseTokenDTO, ? extends Map<String, String>> func) {

        RuntimeContext runtimeContext = CONTEXT.get();
        //设置不拦截请求
        runtimeContext.setIntercept(false);
        //个人站特殊处理
        if (environmentTypeEnum == EnvironmentTypeEnum.UMS_DEV || environmentTypeEnum == EnvironmentTypeEnum.UMS_FAT || environmentTypeEnum == EnvironmentTypeEnum.UMS_STAGE) {
            Map<String, String> map = new HashMap<>(1);
            map.put("Authorization", "Bearer "+getUmsToken(account, password, environmentTypeEnum.getDomain()));
            runtimeContext.setRuntimeHeaders(map);
            runtimeContext.setRuntimeHeadersLastLong(lastLong);
            runtimeContext.setIntercept(true);
            return;
        }
        Forest.config().getVariables().put("token_generate_domain", new ForestVariableValue() {
            @Override
            public Object getValue(ForestMethod method) {
                return environmentTypeEnum.getDomain();
            }
        });
        CommonApi commonApi = SpringUtil.getBean(CommonApi.class);
        BaseGenerateTokenRequestDTO baseGenerateTokenRequestDTO = new BaseGenerateTokenRequestDTO();
        baseGenerateTokenRequestDTO.setAccount(account);
        baseGenerateTokenRequestDTO.setPassword(password);
        BaseTokenDTO token = commonApi.token(baseGenerateTokenRequestDTO);
        List<BaseTokenDTO.OrgInfoDTO> orgList = token.getOrgList();
        Map<String, String> map = func.apply(orgList, token);
        runtimeContext.setRuntimeHeaders(map);
        runtimeContext.setRuntimeHeadersLastLong(lastLong);
        runtimeContext.setIntercept(true);
    }


    /**
     * 会清理设置信息，恢复成默认的状态(常常用于在一个阶段的用户切换之后重置状态使用)
     */
    public static void clear() {
        CONTEXT.remove();
    }


    /**
     * 设置是否拦截接下来的http请求，如果不拦截，则接下来所有的http请求将不会再被自动断言、使用动态token
     *
     * @param isIntercept
     */
    public static void setIntercept(Boolean isIntercept) {
        CONTEXT.get().setIntercept(isIntercept);
    }


    /**
     * 自定义断言请求，忽略json中配置的http状态码
     * example: customRequest(()->{
     * return   api调用
     * },403) 则表示这次请求的预期值是403
     *
     * @param customFunc 请求函数
     * @param httpStatus 预期的http状态码
     * @param <R>        返回值
     * @return java对象
     */
    public static <R extends BaseDto> R customRequest(Supplier<? extends R> customFunc, Integer httpStatus) {
        RuntimeContext runtimeContext = CONTEXT.get();
        runtimeContext.setIntercept(true);
        runtimeContext.setExpectStatusCode(httpStatus);
        return customFunc.get();
    }

    /**
     * 自定义断言请求，忽略json中配置的http状态码
     * example: customRequest(()->{
     * return   api调用
     * },403) 则表示这次请求的预期值是403
     *
     * @param customFunc 请求函数
     * @param httpStatus 预期的http状态码
     * @param isLastLong 是否长期有效
     */
    public static void customRequest(Consumer<RuntimeContext> customFunc, Integer httpStatus, boolean isLastLong) {
        RuntimeContext runtimeContext = CONTEXT.get();
        runtimeContext.setIntercept(true);
        runtimeContext.setExpectStatusCode(httpStatus);
        runtimeContext.setCustomLastLong(isLastLong);
        customFunc.accept(runtimeContext);
    }

    public static RuntimeContext getContext() {
        return CONTEXT.get();
    }

    /**
     * 暂时处理，后续会删除
     *
     * @param mobile   手机号
     * @param password 密码
     * @return token
     */
    @Deprecated
    public static String getUmsToken(String mobile, String password, String domain) {
        Map map = new HashMap();
        map.put("grant_type", "password");
        map.put("client_id", "ums");
        map.put("client_secret", "7da458070e57b98e11d00d9286f23539");
        map.put("os", "mac os");
        map.put("mobile_area_code", "86");
        map.put("mobile", mobile);
        map.put("password", password);

        HttpHeaders headers = new HttpHeaders();
        headers.add("Content-Type", "application/json");
        HttpEntity request = new HttpEntity(map, headers);

        RestTemplate restUtil = new RestTemplate();
        String response = restUtil.postForObject(domain,
                request, String.class);
        return JSONObject.parseObject(response).get("access_token").toString();

    }


    /**
     * 获取上次网络请求拦截到的错误信息
     * 这是一个栈结构，所以如果 你的请求为 a,b,c, 那么a之后调用此方法就是获取a的错误描述， 如果再次获取就是null，只会使用一次
     * 包含error 和 code
     * @return 错误信息
     */
    public static RuntimeContext.ResponseError getError(){
        return CONTEXT.get().getError().pop();
    }


}
